/**************************************************************************
 *                                                                         *
 *  W O R D C L O C K   - A clock that tells the time using words.         *
 *                                                                         *
 * Hardware: Arduino Dumelove with a set of individual LEDs under a word   *
 *            stencil.                                                     *
 *                                                                         *
 *   Copyright (C) 2009  Doug Jackson (doug@doughq.com)                    *
 *   Modified by Justin Shaw 2010                                          *
 *                                                                         *
 ***************************************************************************
 *                                                                         * 
 * This program is free software; you can redistribute it and/or modify    *
 * it under the terms of the GNU General Public License as published by    *
 * the Free Software Foundation; either version 2 of the License, or       *
 * (at your option) any later version.                                     *
 *                                                                         *
 * This program is distributed in the hope that it will be useful,         *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
 * GNU General Public License for more details.                            *
 *                                                                         *
 * You should have received a copy of the GNU General Public License       *
 * along with this program; if not, write to the Free Software             *
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,                   *
 * MA  02111-1307  USA                                                     *
 *                                                                         *
 ***************************************************************************
 * 
 * Revision History
 * 
 * Date  By What
 * 20001025 DRJ Initial Creation of Arduino Version 
 *                      - based on Wordclock.c - from PIC version
 * 20100408 TJS Added Real time clock code.  Converted to DateTime libirary.
 */
#include <Wire.h>
#include <DateTime.h>
#define TIME_MSG_LEN  11   // time sync to PC is HEADER followed by unix time_t as ten ascii digits
#define TIME_HEADER  255   // Header tag for serial time sync message
// Display output pin assignments
#define MINUTES Display1=Display1 | (1<<0)
#define MTEN Display1=Display1 | (1<<1)  
#define HALF Display1=Display1 | (1<<2)
#define PAST Display1=Display1 | (1<<3)
#define THREE Display1=Display1 | (1<<4)
#define ITIS Display1=Display1 | (1<<5)
#define TWENTY Display1=Display1 | (1<<6)
#define TO Display1=Display1 | (1<<7)

#define TWO Display2=Display2 | (1<<0)
#define SIX Display2=Display2 | (1<<1)
#define TWELVE Display2=Display2 | (1<<2)
#define HFIVE Display2=Display2 | (1<<3)
#define SEVEN Display2=Display2 | (1<<4)
#define OCLOCK Display2=Display2 | (1<<5)
#define ONE Display2=Display2 | (1<<6)
#define QUARTER Display2=Display2 | (1<<7)

#define EIGHT Display3=Display3 | (1<<0)
#define MFIVE Display3=Display3 | (1<<1)
#define ARDUINO Display3=Display3 | (1<<2)
#define ELEVEN Display3=Display3 | (1<<3)
#define HTEN Display3=Display3 | (1<<4)
#define NINE Display3=Display3 | (1<<5)
#define FOUR Display3=Display3 | (1<<6)
#define DOUG Display3=Display3 | (1<<7)

int  hour=9, minute=30, second=0;
static unsigned long msTick =0;  // the number of Millisecond Ticks since we last 
// incremented the second counter
int  count;
boolean selftestmode;
char Display1=0, Display2=0, Display3=0;
byte ExtRTC; // Use realtime clock?


// hardware constants
int LEDClockPin=5;
int LEDDataPin=3;
int LEDStrobePin=4;
int FWDButtonPin=6;
int REVButtonPin=7;

void setup()
{
  // initialise the hardware 
  // initialize the appropriate pins as outputs:
  pinMode(LEDClockPin, OUTPUT); 
  pinMode(LEDDataPin, OUTPUT); 
  pinMode(LEDStrobePin, OUTPUT); 
  pinMode(FWDButtonPin, OUTPUT); 
  pinMode(REVButtonPin, INPUT); 

  Serial.begin(9600);   // setup the serial port to 9600 baud
  SWversion();          // Display the version number of the software

  msTick=millis();      // Initialise the msTick counter
  //selftest();
  selftestmode=false;


  // start Wire proto
  Wire.begin();
  DateTime.sync(DateTime.makeTime(second, minute, hour, 1, 1, 2010));
  getTime();

  displaytime();        // display the current time
}



void ledsoff(void) {
  Display1=0;
  Display2=0;
  Display3=0;
}

void WriteLEDs(void) {
  // Now we write the actual values to the hardware
  shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, Display3);
  shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, Display2);
  shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, Display1);
  digitalWrite(LEDStrobePin,HIGH);
  delay(2);
  digitalWrite(LEDStrobePin,LOW);    

}

void selftest(void){
  // start by clearing the display to a known state
  ledsoff(); 
  ITIS; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  MTEN; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  HALF; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  TWENTY; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  QUARTER; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  MFIVE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  MINUTES; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  PAST; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  TO; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  ONE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  TWO; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  THREE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  FOUR; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  HFIVE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  SIX; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  SEVEN; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  EIGHT; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  NINE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  HTEN; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  ELEVEN; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  TWELVE; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  OCLOCK; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  DOUG; 
  WriteLEDs(); 
  delay(500); 
  ledsoff(); 
  ARDUINO; 
  WriteLEDs(); 
  delay(500); 
  for(int i=0; i<5; i++)
  {
    Display1=255; 
    Display2=255; 
    Display3=255;
    WriteLEDs(); 
    delay(500);
    ledsoff(); 
    WriteLEDs();
    delay(500);
  }
}


void displaytime(void){

  // start by clearing the display to a known state
  ledsoff();

  // Now, turn on the "It is" leds
  ITIS;
  Serial.print("It is ");

  // now we display the appropriate minute counter
  if ((minute>4) && (minute<10)) { 
    MFIVE; 
    MINUTES; 
    Serial.print("Five Minutes ");
  } 
  if ((minute>9) && (minute<15)) { 
    MTEN; 
    MINUTES; 
    Serial.print("Ten Minutes ");
  }
  if ((minute>14) && (minute<20)) {
    QUARTER; 
    Serial.print("Quarter ");
  }
  if ((minute>19) && (minute<25)) { 
    TWENTY; 
    MINUTES; 
    Serial.print("Twenty Minutes ");
  }
  if ((minute>24) && (minute<30)) { 
    TWENTY; 
    MFIVE; 
    MINUTES;
    Serial.print("Twenty Five Minutes ");
  }  
  if ((minute>29) && (minute<35)) {
    HALF;
    Serial.print("Half ");
  }
  if ((minute>34) && (minute<40)) { 
    TWENTY; 
    MFIVE; 
    MINUTES;
    Serial.print("Twenty Five Minutes ");
  }  
  if ((minute>39) && (minute<45)) { 
    TWENTY; 
    MINUTES; 
    Serial.print("Twenty Minutes ");
  }
  if ((minute>44) && (minute<50)) {
    QUARTER; 
    Serial.print("Quarter ");
  }
  if ((minute>49) && (minute<55)) { 
    MTEN; 
    MINUTES; 
    Serial.print("Ten Minutes ");
  } 
  if (minute>54) { 
    MFIVE; 
    MINUTES; 
    Serial.print("Five Minutes ");
  }



  if ((minute <5))
  {
    switch (hour % 12) {
    case 1: 
      ONE; 
      Serial.print("One ");
      break;
    case 2: 
      TWO; 
      Serial.print("Two ");
      break;
    case 3: 
      THREE; 
      Serial.print("Three ");
      break;
    case 4: 
      FOUR; 
      Serial.print("Four ");
      break;
    case 5: 
      HFIVE; 
      Serial.print("Five ");
      break;
    case 6: 
      SIX; 
      Serial.print("Six ");
      break;
    case 7: 
      SEVEN; 
      Serial.print("Seven ");
      break;
    case 8: 
      EIGHT; 
      Serial.print("Eight ");
      break;
    case 9: 
      NINE; 
      Serial.print("Nine ");
      break;
    case 10: 
      HTEN; 
      Serial.print("Ten ");
      break;
    case 11: 
      ELEVEN; 
      Serial.print("Eleven ");
      break;
    case 0: 
      TWELVE; 
      Serial.print("Twelve ");
      break;
     default
    }
    OCLOCK;
    Serial.println("O'Clock");
  }
  else
    if ((minute < 35) && (minute >4))
    {
      PAST;
      Serial.print("Past ");
      switch (hour % 12) {
      case 1: 
        ONE; 
        Serial.println("One ");
        break;
      case 2: 
        TWO; 
        Serial.println("Two ");
        break;
      case 3: 
        THREE; 
        Serial.println("Three ");
        break;
      case 4: 
        FOUR; 
        Serial.println("Four ");
        break;
      case 5: 
        HFIVE; 
        Serial.println("Five ");
        break;
      case 6: 
        SIX; 
        Serial.println("Six ");
        break;
      case 7: 
        SEVEN; 
        Serial.println("Seven ");
        break;
      case 8: 
        EIGHT; 
        Serial.println("Eight ");
        break;
      case 9: 
        NINE; 
        Serial.println("Nine ");
        break;
      case 10: 
        HTEN; 
        Serial.println("Ten ");
        break;
      case 11: 
        ELEVEN; 
        Serial.println("Eleven ");
        break;
      case 12: 
        TWELVE; 
        Serial.println("Twelve ");
        break;
      }
    }
    else
    {
      // if we are greater than 34 minutes past the hour then display
      // the next hour, as we will be displaying a 'to' sign
      TO;
      Serial.print("To ");
      switch (hour % 12) {
      case 1: 
        TWO; 
        Serial.println("Two ");
        break;
      case 2: 
        THREE; 
        Serial.println("Three ");
        break;
      case 3: 
        FOUR; 
        Serial.println("Four ");
        break;
      case 4: 
        HFIVE; 
        Serial.println("Five ");
        break;
      case 5: 
        SIX; 
        Serial.println("Six ");
        break;
      case 6: 
        SEVEN; 
        Serial.println("Seven ");
        break;
      case 7: 
        EIGHT; 
        Serial.println("Eight ");
        break;
      case 8: 
        NINE; 
        Serial.println("Nine ");
        break;
      case 9: 
        HTEN; 
        Serial.println("Ten ");
        break;
      case 10: 
        ELEVEN; 
        Serial.println("Eleven ");
        break;
      case 11: 
        TWELVE; 
        Serial.println("Twelve ");
        break;
      case 12: 
        ONE; 
        Serial.println("One ");
        break;
      }
    }



  WriteLEDs();

}

void SWversion(void) {
  //delay(2000);
  Serial.println();
  Serial.println("Wordclock -Arduino v2.0");
  Serial.println("(c)2009 Doug Jackson");
  Serial.println("2010 RealTime clock update by Justin Shaw");
}

void printTime(int hour, int minute, int second){
  Serial.print(hour);
  Serial.print(":");
  if(minute < 10){
    Serial.print("0");
  }
  Serial.print(minute);
  Serial.print(":");
  if(second < 10){
    Serial.print("0");
  }
  Serial.println(second);
}

boolean PCgetTime(){
  // if time sync available from serial port, update time and return true
  boolean status = false;
  while(Serial.available() >=  TIME_MSG_LEN && status == false){  
    // time message consists of a header and ten ascii digits
    if(Serial.read() == TIME_HEADER ) {        
      time_t pctime = 0;
      status = true;  // looks like the start of a time message
      for(int i = 0; i < TIME_MSG_LEN - 1; i++){   
        char c= Serial.read();          
        if( c >= '0' && c <= '9'){   
          pctime = (10 * pctime) + (c - '0') ; // convert digits to a number    
        }
        else{
          status = false; // nope, did not get valid time message
          break;
        }
      }
      if(status){
        DateTime.sync(pctime);   // Sync Arduino clock to the time received on the serial port
        DateTime.available();    
        second = DateTime.Second;
        minute = DateTime.Minute;
        hour = DateTime.Hour;
        Serial.print("PC Time:");
        printTime(hour, minute, second);
        sync_time();     // update RTC if available
        status = true;
      }
    }  
  }
  return status;  //if no message return false
}

// get time from RTC
boolean RTCgetTime(){
  boolean status;
  // reused from macetech.com sample code
  Wire.beginTransmission(104); // 104 is DS3231 device address
  Wire.send(0); // start at register 0
  Wire.endTransmission();
  Wire.requestFrom(104, 3); // request three bytes (second, minute, hour)
  status = Wire.available();
  if(status){
    second = Wire.receive(); // get seconds
    minute = Wire.receive(); // get minutes
    hour = Wire.receive();   // get hours

    second = (((second & 0b11110000)>>4)*10 + 
      (second & 0b00001111)); // convert BCD to decimal
    minute = (((minute & 0b11110000)>>4)*10 + 
      (minute & 0b00001111)); // convert BCD to decimal
    hour = (((hour & 0b00110000)>>4)*10 + 
      (hour & 0b00001111)); // convert BCD to decimal
    Serial.print("RTC: ");
    printTime(hour, minute, second);
  }
  return status;
}

// get time using internal resonator
void INTgetTime(){
  DateTime.available();
  second = DateTime.Second;
  minute = DateTime.Minute;
  hour = DateTime.Hour;
  Serial.print("INT: ");
  printTime(hour, minute, second);
}

/*
 * single front end interface to both INTgetTime() and RTCgetTime()
 * Uses RTC if available or INT if not.
 * Updates time from PC if available
 */
void getTime(){
  if(PCgetTime()){
  }
  else if(RTCgetTime()){
  }
  else{
    INTgetTime();
  }
}

void sync_time(){
  // Only care about hour minute second on this version
  time_t now = DateTime.makeTime(second, minute, hour, 1, 1, 2010);
  DateTime.sync(now);
  if(ExtRTC){
    RTCsetTime(hour, minute, second);
  }
}

// from Beady1.pde from EvilMadScience BulbDail clock
void RTCsetTime(byte hourIn, byte minuteIn, byte secondIn)
{
  Wire.beginTransmission(104); // 104 is DS3231 device address
  Wire.send(0); // start at register 0

    byte ts = secondIn / 10;
  byte os = secondIn - ts*10;
  byte ss = (ts << 4) + os;

  Wire.send(ss); //Send seconds as BCD

  byte tm = minuteIn /10;
  byte om = minuteIn - tm*10;
  byte sm = (tm << 4 ) | om;

  Wire.send(sm); //Send minutes as BCD

  byte th = hourIn /10;
  byte oh = hourIn - th*10;
  byte sh = (th << 4 ) | oh;

  Wire.send(sh); //Send hours as BCD

  Wire.endTransmission();  

}

void interact(){
  boolean fwd, rev;

  fwd = digitalRead(FWDButtonPin);
  rev = digitalRead(REVButtonPin);
  if(fwd && rev){
    selftestmode = !selftestmode;
  }
  else if(fwd){
    Serial.println("FWD, +5 Min");
    minute += 5;
    sync_time();
  }
  else if(rev){
    Serial.println("REV, -5 Min");
    minute -= 5;
    sync_time();
  }
}
void loop(void)
{
  // respond to user input
  interact();
  if(selftestmode){
    selftest();
  }
  else{
    getTime();
    displaytime();
    delay(1000);
  }
}   




